package com.sec.android.app.myfiles.feature.lockunlock;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;

import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.shortcut.ShortcutUtils;
import com.sec.android.app.myfiles.operation.ProgressListener;
import com.sec.android.app.myfiles.util.SemFwWrapper;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Locale;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class LockUnlockImp {
    private static final String TAG = "LockUnlockImp";
    private static final String CONFIRM_ENCRYPTION = "confirm_encryption";
    private static final String ENCRYPTION_ALGORITHM = "AES";
    private static final String DEFAULT_ALG_MODE = ENCRYPTION_ALGORITHM + "/ECB/PKCS5Padding";
    private static final String HEX = "0123456789ABCDEF";

    private static int BUFFER_SIZE = 1024 * 2;
    private static String RAW_ENC_EXT = "enc";
    private static String ENC_EXT = "loc";

    private boolean mCancel = false;
    private boolean mIsDeviceSeperator = false;
    private boolean mIsOldDeviceSeperator = false;

    public LockUnlockImp() {

    }

    private String getOldEncKey(Context context, boolean rawKey) {
        String ret = null;

        if (!rawKey) {
            String defaultkey = getOldDeviceSeperator(context, rawKey);

            if (defaultkey != null) {
                int key = defaultkey.hashCode() + 5;
                key = Math.abs(key);
                ret = String.format(Locale.getDefault(), "%010d%010d%010d28", key, key, key);
            }
        }

        if (ret == null) {
            ret = "12345678901234567890123456789012";
        }

        return ret;
    }

    public static String toSHA1(String value) {
        if (value != null && !value.isEmpty()) {
            String sha1 = "";
            try {
                MessageDigest crypt = MessageDigest.getInstance("SHA-1");
                crypt.reset();
                crypt.update(value.getBytes("UTF-8"));
                sha1 = toHex(crypt.digest());
            } catch (Exception e) {
                Log.e(TAG, "Exception:" + e.toString());
            }
            return sha1;
        }
        return null;
    }

    public static String toHex(byte[] buf) {
        if (buf == null) {
            return "";
        }
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (byte bufferElement : buf) {
            appendHex(result, bufferElement);
        }
        String hexToString = result.toString();
        return hexToString;
    }

    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }

    public void setDoNotShowConfirmDialog(Context context) {
        SharedPreferences.Editor e = PreferenceManager.getDefaultSharedPreferences(context).edit();
        e.putBoolean(CONFIRM_ENCRYPTION, true);
        e.apply();
    }

    public boolean getDoNotShowConfirmDialog(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(CONFIRM_ENCRYPTION, false);
    }

    private String getDecFileName(FileRecord record) {
        String srcPath = record.getFullPath();
        String ret = srcPath.substring(0, srcPath.lastIndexOf('.'));
        return getUniqueName(ret);
    }

    private String getOldDeviceSeperator(Context context, boolean bRaw) {
        String ret;

        ret = getDeviceKey(context);

        if (!bRaw) {
            if (ret != null) {
                int key = ret.hashCode() + 10;
                key = Math.abs(key);
                ret = String.format(Locale.getDefault(), "130%010d228", key);
            }
        }

        return ret;
    }

    private String getDeviceSeperator(Context context) {
        String ret;

        ret = getDeviceKey(context);

        if (ret != null) {
            String sha1 = toSHA1(ret);
            ret = sha1;
        }

        return ret;
    }

    public void cancel() {
        mCancel = true;
    }

    private SecretKeySpec generatePBKDF2SecretKey(String sessionKey, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException {
        if (sessionKey == null) {
            return null;
        }
        int iterationCount = 1000;
        int keyLength = 256;
        char[] chars = sessionKey.toCharArray();

        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        PBEKeySpec keySpec = new PBEKeySpec(chars, salt, iterationCount, keyLength);
        SecretKey key = keyFactory.generateSecret(keySpec);

        return new SecretKeySpec(key.getEncoded(), ENCRYPTION_ALGORITHM);
    }

    private SecretKeySpec createSecurityKeyPBKDF2(String sessionKey, byte[] salt) {
        SecretKeySpec key = null;
        try {
            key = generatePBKDF2SecretKey(sessionKey, salt);
        } catch (NoSuchAlgorithmException e) {
            Log.e(this, "Exception " + e.getMessage());
        } catch (InvalidKeySpecException e) {
            Log.e(this, "Exception " + e.getMessage());
        } catch (NoSuchPaddingException e) {
            Log.e(this, "Exception " + e.getMessage());
        }
        return key;
    }

    public boolean decFile(Context context, FileRecord record, ProgressListener listener) {
        boolean bRet = false;
        File srcFile = SemFwWrapper.file(record.getFullPath());
        File desFile = SemFwWrapper.file(getDecFileName(record));

        mCancel = false;

        Log.i(this, "decFile srcPath: " + srcFile.getAbsolutePath() + "  lastDesName: " + desFile.getAbsolutePath());

        boolean bRaw = false;
        if (RAW_ENC_EXT.equalsIgnoreCase(record.getExt())) {
            bRaw = true;
        }
        String deviceSeperator = getDeviceSeperator(context);
        if (deviceSeperator != null) {
            byte[] salt = deviceSeperator.getBytes(Charset.defaultCharset());
            SecretKeySpec key = createSecurityKeyPBKDF2(getDeviceKey(context), salt);

            String oldDeviceSeperator = getOldDeviceSeperator(context, bRaw);
            byte[] byteKey = createSecurityKey(getOldEncKey(context, bRaw));
            SecretKeySpec oldKey = new SecretKeySpec(byteKey, ENCRYPTION_ALGORITHM);


            Cipher cipher = null;
            try {
                cipher = Cipher.getInstance(DEFAULT_ALG_MODE);
            } catch (Exception e) {
                Log.e(this, "Exception " + e.getMessage());
            }

            if (key != null && cipher != null && oldDeviceSeperator != null) {
                checkDeviceSeperator(srcFile, desFile, deviceSeperator, oldDeviceSeperator);
                if (mIsOldDeviceSeperator) {
                    bRet = writeDecFile(srcFile, desFile, cipher, oldKey, oldDeviceSeperator, listener, context, record);
                } else if (mIsDeviceSeperator) {
                    bRet = writeDecFile(srcFile, desFile, cipher, key, deviceSeperator, listener, context, record);
                }
            }
        }
        return bRet;
    }

    private boolean writeDecFile(File srcFile, File desFile, Cipher cipher, SecretKeySpec key, String deviceSeperator, ProgressListener listener, Context context, FileRecord record) {
        boolean bRet = true;
        String deviceSeperatorInEncFileToString = null;

        int seperatorLength = deviceSeperator.length();

        if (seperatorLength > 0) {
            byte[] getDeviceSeperatorFromEncFile = new byte[seperatorLength];

            try (InputStream is = new BufferedInputStream(new FileInputStream(srcFile));
                 OutputStream os = new BufferedOutputStream(new FileOutputStream(desFile))) {

                long fileSize = srcFile.length();
                long numberofParms = fileSize / BUFFER_SIZE;
                int index = 0;
                byte[] buffer = new byte[BUFFER_SIZE];

                listener.onProgressChanged(null, 0, 100);

                if (is.read(getDeviceSeperatorFromEncFile) != -1) {
                    deviceSeperatorInEncFileToString = new String(getDeviceSeperatorFromEncFile, Charset.defaultCharset());
                }

                cipher.init(Cipher.DECRYPT_MODE, key);

                if (deviceSeperator.equals(deviceSeperatorInEncFileToString)) {
                    int readCnt;
                    while ((readCnt = is.read(buffer)) != -1) {
                        if (mCancel) {
                            bRet = false;
                            break;
                        }
                        byte[] buff = cipher.update(buffer, 0, readCnt);
                        if (buff != null) {
                            os.write(buff);
                        }
                        listener.onProgressChanged(null, getCurPercent(index++, (int) numberofParms), 100);
                    }
                    os.write(cipher.doFinal());
                }

            } catch (Exception e) {
                Log.e(this, "Exception:" + e.toString());
                bRet = false;
            } finally {
                if (bRet)
                    ShortcutUtils.deleteShortcutFromHome(context, record);
                clearFile(srcFile, desFile, bRet);
            }
        } else {
            bRet = false;
        }

        return bRet;

    }

    private void checkDeviceSeperator(File srcFile, File desFile, String deviceSeperator, String oldDeviceSeperator) {
        mIsDeviceSeperator = false;
        mIsOldDeviceSeperator = false;

        String deviceSeperatorInEncFileToString = null;
        String oldDeviceSeperatorInEncFileToString = null;

        int seperatorLength = deviceSeperator.length();
        int oldSeperatorLength = oldDeviceSeperator.length();

        if (seperatorLength > 0 && oldSeperatorLength > 0) {
            byte[] getDeviceSeperatorFromEncFile = new byte[seperatorLength];

            try (InputStream is = new BufferedInputStream(new FileInputStream(srcFile));
                 OutputStream os = new BufferedOutputStream(new FileOutputStream(desFile))) {

                if (is.read(getDeviceSeperatorFromEncFile) != -1) {
                    deviceSeperatorInEncFileToString = new String(getDeviceSeperatorFromEncFile, Charset.defaultCharset());
                    oldDeviceSeperatorInEncFileToString = deviceSeperatorInEncFileToString.substring(0, oldSeperatorLength);
                }
                if (deviceSeperator.equals(deviceSeperatorInEncFileToString)) {
                    mIsDeviceSeperator = true;
                } else if (oldDeviceSeperator.equals(oldDeviceSeperatorInEncFileToString)) {
                    mIsOldDeviceSeperator = true;
                }
            } catch (Exception e) {
                Log.e(this, "Exception:" + e.toString());
            }
        }
    }

    private void clearFile(File src, File dest, boolean bSuccess) {
        File deleteFile = dest;

        if (bSuccess) {
            deleteFile = src;
        }

        if (deleteFile.delete() == false) {
            Log.d(this, "clearFile : delete (" + deleteFile.getName() + ") failed ");
        }

    }

    private String getUniqueName(String fullName) {
        int i = 0;
        while ((SemFwWrapper.file(fullName)).exists()) {
            fullName = addPostfix(fullName, i);
            i++;
        }
        return fullName;
    }

    private String addPostfix(String fullName, int postfixNum) {
        String resultStr;
        String encExt = "";

        if (isEncryptionFile(fullName)) {
            int index = fullName.lastIndexOf('.');
            if (index != -1) {
                encExt = fullName.substring(index, fullName.length());
                fullName = fullName.substring(0, index);
            }
        }
        int lastDot = fullName.lastIndexOf('.');
        if (lastDot != -1) {
            String name = fullName.substring(0, lastDot);
            String ext = fullName.substring(lastDot, fullName.length());

            StringBuilder sb = new StringBuilder(name);
            sb.append(" (").append(postfixNum).append(')').append(ext).append(encExt);
            resultStr = sb.toString();
        } else {
            StringBuilder sb = new StringBuilder(fullName);
            sb.append(" (").append(postfixNum).append(')').append(encExt);
            resultStr = sb.toString();
        }
        return resultStr;
    }

    private int getCurPercent(int cur, int total) {
        if (total == 0) {
            return 100;
        }
        return (cur * 100) / total;
    }

    public boolean isEncryptionFile(String fileStr) {
        if (fileStr != null) {
            String extension = fileStr.substring(fileStr.lastIndexOf('.') + 1, fileStr.length());
            if (ENC_EXT.equalsIgnoreCase(extension) || RAW_ENC_EXT.equalsIgnoreCase(extension)) {
                return true;
            }
        }

        return false;
    }

    private byte[] createSecurityKey(String szKey) {
        /*
         * To Sun Ning 
         * Because of Prevent issue, below code is changed. 
         * If there is the problem of logic. Please modify this without Prevent issue.
         * And if there is no problem in logic. Please remove this comment. 
         *  
         *  int radix = 16;
         *  int divLen = (radix == 16) ? 2 : 3;
         */
        final int radix = 16;
        final int divLen = 2;

        int lenKeyString = szKey.length();
        if ((lenKeyString > 0) && (lenKeyString % divLen == 1))
            return null;

        lenKeyString = lenKeyString / divLen;

        byte[] byteKey = new byte[lenKeyString];
        for (int i = 0; i < lenKeyString; i++) {
            int idx = i * divLen;
            byteKey[i] = (byte) (Short.parseShort(szKey.substring(idx, idx + divLen), radix));
        }

        return byteKey;
    }

    private String getDeviceKey(Context context) {
        String ret = null;

        if (isWifiDevice(context))
            ret = getSerialNumber();
        else
            ret = getDeviceID(context);

        return ret;
    }

    private String getDeviceID(Context context) {
        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

        return telephonyManager.getDeviceId();

    }

    private String getSerialNumber() {
        String serialNum = null;
        try {
            //Class<?> classZ = Class.forName("android.os.SystemProperties");
            Class<?> classZ = Class.forName("android.os.SemSystemProperties");
            Method get = classZ.getMethod("get", String.class);
            serialNum = (String) get.invoke(classZ, "ro.serialno");
        } catch (Exception e) {
            Log.e(this, "Exception:" + e.toString());
        }
        return serialNum;
    }

    private boolean isWifiDevice(Context context) {
        TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        int type = telephony.getPhoneType();
        if (type == TelephonyManager.PHONE_TYPE_NONE) {
            return true;
        } else {
            return false;
        }
    }

}
